<dom-module id="grid-drag-and-drop-demos">
  <template>
    <style include="vaadin-component-demo-shared-styles">
      :host {
        display: block;
      }
    </style>

    <h3>Row Reordering</h3>
    <p>
      Switch on the <code>rowsDraggable</code> flag to make the grid's rows draggable with mouse (or touch).
    </p>
    <p>
      Specifying <code>dropMode</code> enables dropping on top of the grid or grid rows.
      Supported values for the <code>dropMode</code> are
      <ul>
        <li><b>on-grid</b> <i>allows dropping on top of the grid</i></li>
        <li><b>on-top</b> <i>allows dropping on top of the grid rows</i></li>
        <li><b>between</b> <i>allows dropping between the grid rows</i></li>
        <li><b>on-top-or-between</b> <i>allows dropping on top of or between the grid rows</i></li>
      </ul>
    </p>
    <p>
      Depending on <code>rowsDraggable</code> and <code>dropMode</code> configuration,
      grid fires events during the different phases of drag and drop sequence.
      <ul>
        <li><b>grid-dragstart</b> <i>fired when starting to drag grid rows</i></li>
        <li><b>grid-dragend</b> <i>fired when the dragging of the rows ends</i></li>
        <li><b>grid-drop</b> <i>fired when a drop occurs on top of the grid</i></li>
      </ul>
    </p>
    <vaadin-demo-snippet id="grid-drag-and-drop-demos-row-reordering" when-defined="vaadin-grid">
      <template preserve-content>
        <vaadin-grid rows-draggable>
          <vaadin-grid-column path="name.first" header="First name"></vaadin-grid-column>
          <vaadin-grid-column path="name.last" header="Last name"></vaadin-grid-column>
          <vaadin-grid-column path="email"></vaadin-grid-column>
        </vaadin-grid>

        <script>
          window.addDemoReadyListener('#grid-drag-and-drop-demos-row-reordering', function(document) {
            // Assign an array of user objects as the grid's items
            const grid = document.querySelector('vaadin-grid');
            grid.items = Vaadin.GridDemo.users.slice(0);

            let draggedItem;

            grid.addEventListener('grid-dragstart', function(e) {
              draggedItem = e.detail.draggedItems[0];
              grid.dropMode = 'between';
            });

            grid.addEventListener('grid-dragend', function(e) {
              draggedItem = grid.dropMode = null;
            });

            grid.addEventListener('grid-drop', function(e) {
              const dropTargetItem = e.detail.dropTargetItem;
              if (draggedItem && draggedItem !== dropTargetItem) {
                // Reorder the items
                const items = grid.items.filter(function(i) {
                  return i !== draggedItem;
                });
                const dropIndex = items.indexOf(dropTargetItem)
                  + (e.detail.dropLocation === 'below' ? 1 : 0);
                items.splice(dropIndex, 0, draggedItem);
                grid.items = items;
              }
            });

          });
        </script>
      </template>
    </vaadin-demo-snippet>

    <h3>Drag Rows Between Grids</h3>
    <p>
      By default, only one row gets dragged at once. Make the grid rows selectable,
      for example with the selection column helper, to enable dragging multiple rows at the same time.
    </p>
    <p>
      The <code>detail.draggedItems</code> property of the <code>grid-dragstart</code> event
      is an array containing the items in the visible viewport that are dragged.
    </p>
    <p>
      The <code>detail.dropTargetItem</code> property of the <code>grid-drop</code> event
      refers to the item of the grid row on which the drop occurred.
    </p>

    <vaadin-demo-snippet id="grid-drag-and-drop-demos-drag-between-grids" when-defined="vaadin-grid">
      <template preserve-content>
        <div style="display: flex;">
          <vaadin-grid rows-draggable>
            <vaadin-grid-selection-column auto-select></vaadin-grid-selection-column>
            <vaadin-grid-column path="name.first" header="First name"></vaadin-grid-column>
            <vaadin-grid-column path="name.last" header="Last name"></vaadin-grid-column>
            <vaadin-grid-column path="email"></vaadin-grid-column>
          </vaadin-grid>

          &nbsp;

          <vaadin-grid rows-draggable>
            <vaadin-grid-selection-column auto-select></vaadin-grid-selection-column>
            <vaadin-grid-column path="name.first" header="First name"></vaadin-grid-column>
            <vaadin-grid-column path="name.last" header="Last name"></vaadin-grid-column>
            <vaadin-grid-column path="email"></vaadin-grid-column>
          </vaadin-grid>
        </div>
        <script>
          window.addDemoReadyListener('#grid-drag-and-drop-demos-drag-between-grids', function(document) {
            let draggedItems;
            let sourceGrid;
            const grids = Array.from(document.querySelectorAll('vaadin-grid'));

            const dragStartListener = function(e) {
              draggedItems = e.detail.draggedItems;
              sourceGrid = e.target;
              grids.forEach(function(grid) {
                grid.dropMode = 'between';
              });
            };

            const dragEndListener = function(e) {
              draggedItems = sourceGrid = null;
              grids.forEach(function(grid) {
                grid.dropMode = null;
              });
            };

            const dropListener = function(e) {
              const dropTargetItem = e.detail.dropTargetItem;
              if (draggedItems.indexOf(dropTargetItem) === -1) {
                // Remove the items from the source grid
                sourceGrid.items = sourceGrid.items.filter(function(i) {
                  return draggedItems.indexOf(i) === -1;
                });
                sourceGrid.selectedItems = [];

                // Add dragged items to the target Grid
                const targetGrid = e.target;
                let index = targetGrid.items.length;
                if (dropTargetItem) {
                  index = targetGrid.items.indexOf(dropTargetItem)
                    + (e.detail.dropLocation === 'below' ? 1 : 0);
                }
                targetGrid.items = []
                  .concat(targetGrid.items.slice(0, index))
                  .concat(draggedItems)
                  .concat(targetGrid.items.slice(index));
              }
            };

            grids.forEach(function(grid, index) {
              grid.items = index === 0 ? Vaadin.GridDemo.users.slice(0, 5) : Vaadin.GridDemo.users.slice(6, 7);
              grid.addEventListener('grid-dragstart', dragStartListener);
              grid.addEventListener('grid-dragend', dragEndListener);
              grid.addEventListener('grid-drop', dropListener);
            });

          });
        </script>
      </template>
    </vaadin-demo-snippet>

    <h3>Drop Location</h3>
    <p>
      The <code>detail.dropLocation</code> property of the <code>grid-drop</code> event
      indicates the position at which the drop event took place relative to a row. Depending
      on the <code>dropMode</code> value, the drop location can be one of the following:

      <ul>
        <li><b>on-top</b> <i>when the drop occurred on top of the row</i></li>
        <li><b>above</b> <i>when the drop occurred above the row</i></li>
        <li><b>below</b> <i>when the drop occurred below the row</i></li>
        <li><b>empty</b> <i>when the drop occurred over the grid, not relative to any specific row</i></li>
      </ul>
    </p>

    <p>
      <b>Hint: Try dragging users from the grid on the left on top of the users of the grid on
        the right to create a hierarchical structure.</b>
    </p>

    <vaadin-demo-snippet id="grid-drag-and-drop-demos-drop-location" when-defined="vaadin-grid">
      <template preserve-content>
        <div style="display: flex;">
          <vaadin-grid id="source" rows-draggable>
            <vaadin-grid-column path="name.first" header="First name"></vaadin-grid-column>
            <vaadin-grid-column path="name.last" header="Last name"></vaadin-grid-column>
            <vaadin-grid-column path="email"></vaadin-grid-column>
          </vaadin-grid>

          &nbsp;

          <vaadin-grid id="target">
            <vaadin-grid-tree-column path="name.first" header="First name"></vaadin-grid-tree-column>
            <vaadin-grid-column path="name.last" header="Last name"></vaadin-grid-column>
            <vaadin-grid-column path="email"></vaadin-grid-column>
          </vaadin-grid>
        </div>
        <script>
          window.addDemoReadyListener('#grid-drag-and-drop-demos-drop-location', function(document) {
            const sourceGrid = document.querySelector('#source');
            const targetGrid = document.querySelector('#target');

            const clone = function(user) {
              return Object.assign({}, user);
            };
            sourceGrid.items = Vaadin.GridDemo.users.slice(2).map(clone);
            const targetGridItems = Vaadin.GridDemo.users.slice(0, 2).map(clone);

            targetGrid.dataProvider = function(params, cb) {
              const items = params.parentItem ? params.parentItem.children : targetGridItems;
              const start = params.pageSize * params.page;
              cb(items.slice(start, start + params.pageSize), items.length);
            };

            let draggedItem;

            sourceGrid.addEventListener('grid-dragstart', function(e) {
              draggedItem = e.detail.draggedItems[0];
              targetGrid.dropMode = 'on-top-or-between';
            });

            sourceGrid.addEventListener('grid-dragend', function() {
              draggedItem = targetGrid.dropMode = null;
            });

            targetGrid.addEventListener('grid-drop', function(e) {
              sourceGrid.items = sourceGrid.items.filter(function(i) {
                return i !== draggedItem;
              });

              const dropTargetItem = e.detail.dropTargetItem;

              if (e.detail.dropLocation === 'on-top') {
                draggedItem.parentItem = dropTargetItem;
                dropTargetItem.children = (dropTargetItem.children || []).concat(draggedItem);
              } else {
                const siblings = dropTargetItem.parentItem ? dropTargetItem.parentItem.children : targetGridItems;
                const dropIndex = siblings.indexOf(dropTargetItem)
                  + (e.detail.dropLocation === 'below' ? 1 : 0);
                siblings.splice(dropIndex, 0, draggedItem);
              }
              targetGrid.clearCache();
            });

          });
        </script>
      </template>
    </vaadin-demo-snippet>

    <h3>Custom Drag Data</h3>
    <p>
      Operating with the drag event text data enables you to process drag and drop between different application windows.
      The default payload of the drag event is generated from the visible grid columns and items as a line break
      separated list of tab-separated values.
    </p>
    <p>
      The <code>detail.data</code> property of the <code>grid-drop</code> event
      is the payload of the drag and drop operation.
    </p>
    <p>
      To customize the drag event data, use the <code>detail.setDragData</code> function of the <code>grid-dragstart</code> event.
      The function takes two parameters:
      <ul>
        <li><b>type:string</b> <i>The type of the data</i></li>
        <li><b>data:string</b> <i>The data</i></li>
      </ul>
    </p>
    <p>
      <b>Note:</b> "text" is the only data type supported by all the browsers the grid currently supports (including IE11).
    </p>
    <p>
      <b>Note:</b> On multi selection, only the rows in the viewport are included in the drag operation.
    </p>
    <p>
      <b>Hint:</b> Try dragging the grid's rows to the grid of the same demo but on another browser window.
    </p>
    <vaadin-demo-snippet id="grid-drag-and-drop-demos-drag-data" when-defined="vaadin-grid">
      <template preserve-content>
        <div style="display: flex;">
          <vaadin-grid drop-mode="between" rows-draggable>
            <vaadin-grid-selection-column auto-select></vaadin-grid-selection-column>
            <vaadin-grid-column path="name.first" header="First name"></vaadin-grid-column>
            <vaadin-grid-column path="name.last" header="Last name"></vaadin-grid-column>
            <vaadin-grid-column path="email"></vaadin-grid-column>
          </vaadin-grid>
        </div>

        <script>
          window.addDemoReadyListener('#grid-drag-and-drop-demos-drag-data', function(document) {
            // Assign an array of user objects as the grid's items
            const grid = document.querySelector('vaadin-grid');
            grid.items = Vaadin.GridDemo.users.slice(0);

            let draggedItems;
            grid.addEventListener('grid-dragstart', function(e) {
              // We'll use grid.selectedItems since e.detail.draggedItems only contains the visible dragged items

              // e.detail.draggedItems can only contain items from the visible
              // viewport, so when the user is dragging the selected items, let's
              // instead treat the whole selection (not just the visible items) as
              // dragged items
              const draggingSelected = grid.selectedItems.indexOf(e.detail.draggedItems[0]) > -1;
              draggedItems = draggingSelected ? grid.selectedItems : e.detail.draggedItems;
              if (draggedItems.length > 1) {
                e.detail.setDraggedItemsCount(draggedItems.length);
              }

              const data = '[' + draggedItems.map(function(item) {
                return JSON.stringify(item);
              }).join(',') + ']';
              e.detail.setDragData('text', data);
            });

            grid.addEventListener('grid-drop', function(e) {
              // Restore the dragged items from the drag data object
              const draggedItems = JSON.parse(e.detail.dragData[0].data);

              const targetGrid = e.target;
              let index = targetGrid.items.length;
              const dropTargetItem = e.detail.dropTargetItem;
              if (dropTargetItem) {
                index = targetGrid.items.indexOf(dropTargetItem)
                  + (e.detail.dropLocation === 'below' ? 1 : 0);
              }
              targetGrid.items = []
                .concat(targetGrid.items.slice(0, index))
                .concat(draggedItems)
                .concat(targetGrid.items.slice(index));

            });

            grid.addEventListener('grid-dragend', function(e) {
              // Remove the items from the source grid.
              // Additional server-side logic might be required to validate if this should actually happen.
              // The demo just removes the items regardless of whether the drop was legal.
              grid.items = grid.items.filter(function(item) {
                return draggedItems.indexOf(item) === -1;
              });
              grid.selectedItems = [];
            });
          });
        </script>
      </template>
    </vaadin-demo-snippet>

    <h3>Drag and Drop Filters</h3>
    <p>
      When <code>rows-draggable</code> and <code>drop-mode</code> properties are set, by default, all the grid rows
      can be dragged and dropped on top of. This can be controlled per row basis by using <code>dragFilter</code>
      and <code>dropFilter</code> hook functions.
    </p>
    <p>
      The <code>dragFilter</code> function receives information about the row as the argument and
      should return a boolean value indicating whether dragging of the corresponding row should be enabled.
      Respectively, <code>dropFilter</code> can be used for disabling dropping on top of specific rows.
    </p>

    <p>
      <b>
        Hint: Try changing the subordinates of the main level supervisors by dragging. The filters prevent
        <ul>
          <li>dragging the supervisor rows</li>
          <li>dropping on top of the same supervisor the subordinate is already under</li>
          <li>dropping more than 4 subordinates under one supervisor</li>
          <li>dropping on top of the subordinates</li>
        </ul>
      </b>
    </p>
    <vaadin-demo-snippet id="grid-drag-and-drop-demos-filters" when-defined="vaadin-grid">
      <template preserve-content>
        <b>Supervisors</b>
        <vaadin-grid rows-draggable>
          <vaadin-grid-tree-column path="name.first" header="First name" item-has-children-path="subordinates"></vaadin-grid-tree-column>
          <vaadin-grid-column path="name.last" header="Last name"></vaadin-grid-column>
          <vaadin-grid-column path="email"></vaadin-grid-column>
        </vaadin-grid>

        <script>
          window.addDemoReadyListener('#grid-drag-and-drop-demos-filters', function(document) {
            const grid = document.querySelector('vaadin-grid');
            let draggedUser;

            const clone = function(user) {
              return Object.assign({}, user);
            };
            const supervisors = Vaadin.GridDemo.users.slice(0, 3).map(clone);
            supervisors[0].subordinates = Vaadin.GridDemo.users.slice(3, 6).map(clone);
            supervisors[1].subordinates = Vaadin.GridDemo.users.slice(6, 8).map(clone);
            supervisors[2].subordinates = [];

            grid.dataProvider = function(params, cb) {
              const items = params.parentItem ? params.parentItem.subordinates : supervisors;
              const start = params.pageSize * params.page;
              cb(items.slice(start, start + params.pageSize), items.length);
            };

            grid.dragFilter = function(model) {
              // Disallow dragging supervisors
              return !model.item.subordinates;
            };

            grid.dropFilter = function(model) {
              return model.item.subordinates // Only support dropping on top of supervisors
                && model.item.subordinates.length < 4 // Don't allow more than 4 subordinates
                && model.item.subordinates.indexOf(draggedUser) === -1; // Disallow dropping on own supervisor
            };

            grid.expandedItems = Array.from(supervisors);

            grid.addEventListener('grid-dragstart', function(e) {
              draggedUser = e.detail.draggedItems[0];
              // Enabling the drop mode on drag start. This will
              // also automatically re-run any drag and drop filters on the grid.
              grid.dropMode = 'on-top';
            });

            grid.addEventListener('grid-dragend', function() {
              draggedUser = grid.dropMode = null;
            });

            grid.addEventListener('grid-drop', function(e) {
              const supervisor = e.detail.dropTargetItem;
              if (supervisor) {
                // Remove the item from it's previous supervisor's subordinates list
                supervisors.forEach(function(supervisor) {
                  supervisor.subordinates = supervisor.subordinates.filter(function(subordinate) {
                    return subordinate !== draggedUser;
                  });
                });

                // Add the item to the target supervisor's subordinates list
                supervisor.subordinates.push(draggedUser);
                grid.clearCache();
              }
            });

          });
        </script>
      </template>
    </vaadin-demo-snippet>

  </template>
  <script>
    class GridDragAndDropDemos extends DemoReadyEventEmitter(GridDemo(Polymer.Element)) {
      static get is() {
        return 'grid-drag-and-drop-demos';
      }
    }
    customElements.define(GridDragAndDropDemos.is, GridDragAndDropDemos);
  </script>
</dom-module>
